Completed
Push — master ( 00d23f...d25395 )
by Jan
15s queued 12s
created

ID3Frame.createFromBuffer   C

Complexity

Conditions 11

Size

Total Lines 44
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 30
dl 0
loc 44
rs 5.4
c 0
b 0
f 0
cc 11

How to fix   Complexity   

Complexity

Complex classes like ID3Frame.createFromBuffer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
const zlib = require('zlib')
2
const ID3FrameHeader = require('./ID3FrameHeader')
3
const ID3Frames = require('./ID3Frames')
4
const ID3Util = require('./ID3Util')
5
6
class ID3Frame {
7
    constructor(identifier, value, flags = {}) {
8
        this.identifier = identifier
9
        this.value = value
10
        this.dataLengthIndicator = 0
11
        this.flags = flags
12
    }
13
14
    static createFromBuffer(frameBuffer, version) {
15
        const frameHeaderSize = ID3FrameHeader.getHeaderSize(version)
16
        // Specification requirement
17
        if(frameBuffer < frameHeaderSize + 1) {
18
            return null
19
        }
20
        const frameHeaderBuffer = frameBuffer.subarray(0, frameHeaderSize)
21
        const frameHeader = ID3FrameHeader.createFromBuffer(frameHeaderBuffer, version)
22
        if(frameHeader.flags.encryption) {
23
            return null
24
        }
25
26
        const frameBodyOffset = frameHeader.flags.dataLengthIndicator ? 4 : 0
27
        const frameBodyStart = frameHeaderSize + frameBodyOffset
28
        let frameBody = frameBuffer.subarray(frameBodyStart, frameBodyStart + frameHeader.bodySize - frameBodyOffset)
29
        if(frameHeader.flags.unsynchronisation) {
30
            // This method should stay in ID3Util for now because it's also used in the Tag's header which we don't have a class for.
31
            frameBody = ID3Util.processUnsynchronisedBuffer(frameBody)
32
        }
33
        if(frameHeader.flags.dataLengthIndicator) {
34
            this.dataLengthIndicator = frameBuffer.readInt32BE(frameHeaderSize)
35
        }
36
        if(frameHeader.flags.compression) {
37
            const uncompressedFrameBody = decompressBodyBuffer(frameBody, this.dataLengthIndicator)
38
            if(!uncompressedFrameBody) {
39
                return null
40
            }
41
            frameBody = uncompressedFrameBody
42
        }
43
44
        let value = null
0 ignored issues
show
Unused Code introduced by
The assignment to value seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
45
        if(ID3Frames[frameHeader.identifier]) {
46
            value = ID3Frames[frameHeader.identifier].read(frameBody, version)
47
        } else if(frameHeader.identifier.startsWith('T')) {
48
            value = ID3Frames.GENERIC_TEXT.read(frameBody, version)
49
        } else if(frameHeader.identifier.startsWith('W')) {
50
            value = ID3Frames.GENERIC_URL.read(frameBody, version)
51
        } else {
52
            // Unknown frames are not supported currently.
53
            return null
54
        }
55
56
        return new ID3Frame(frameHeader.identifier, value, frameHeader.flags)
57
    }
58
59
    getBuffer() {
60
        if(ID3Frames[this.identifier]) {
61
            return ID3Frames[this.identifier].create(this.value)
62
        }
63
        if(this.identifier.startsWith('T')) {
64
            return ID3Frames.GENERIC_TEXT.create(this.value)
65
        }
66
        if(this.identifier.startsWith('W')) {
67
            return ID3Frames.GENERIC_URL.create(this.value)
68
        }
69
70
        return null
71
    }
72
73
    getValue() {
74
        return this.value
75
    }
76
}
77
78
function decompressBodyBuffer(bodyBuffer, dataLengthIndicator) {
79
    if(bodyBuffer.length < 5 || dataLengthIndicator === undefined) {
80
        return null
81
    }
82
83
    /*
84
    * ID3 spec defines that compression is stored in ZLIB format, but doesn't specify if header is present or not.
85
    * ZLIB has a 2-byte header.
86
    * 1. try if header + body decompression
87
    * 2. else try if header is not stored (assume that all content is deflated "body")
88
    * 3. else try if inflation works if the header is omitted (implementation dependent)
89
    * */
90
    let decompressedBody
91
    try {
92
        decompressedBody = zlib.inflateSync(bodyBuffer)
93
    } catch (e) {
94
        try {
95
            decompressedBody = zlib.inflateRawSync(bodyBuffer)
96
        } catch (e) {
97
            try {
98
                decompressedBody = zlib.inflateRawSync(bodyBuffer.subarray(2))
99
            } catch (e) {
100
                return null
101
            }
102
        }
103
    }
104
    if(decompressedBody.length !== dataLengthIndicator) {
105
        return null
106
    }
107
    return decompressedBody
108
}
109
110
module.exports = ID3Frame
111